package org.zjvis.datascience.common.dto;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.zjvis.datascience.common.algo.BaseAlg;
import org.zjvis.datascience.common.constant.DataJsonConstant;
import org.zjvis.datascience.common.enums.*;
import org.zjvis.datascience.common.etl.BaseETL;
import org.zjvis.datascience.common.exception.AutoTriggerException;
import org.zjvis.datascience.common.exception.SqlParserException;
import org.zjvis.datascience.common.model.ApiResultCode;
import org.zjvis.datascience.common.util.CollectionUtil;
import org.zjvis.datascience.common.util.DozerUtil;
import org.zjvis.datascience.common.util.TaskUtil;
import org.zjvis.datascience.common.util.ToolUtil;
import org.zjvis.datascience.common.vo.TaskVO;
import org.zjvis.datascience.common.widget.dto.WidgetDTO;
import org.zjvis.datascience.common.widget.enums.WidgetTypeEnum;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.zjvis.datascience.common.constant.DataJsonConstant.FORBIDDEN_FLAG;
import static org.zjvis.datascience.common.enums.TaskTypeEnum.*;

/**
 * @description 任务节点Task信息表，任务节点DTO
 * @date 2021-12-17
 */
@Data
public class TaskDTO extends BaseDTO implements Cloneable {

    private Long id;

    private String name;

    private Long projectId;

    private Long pipelineId;

    //多个用逗号隔开
    private String parentId;
    // 多个用逗号隔开
    private String childId;

    private Integer type;

    private Long userId;

    private String dataJson;

    private Long operatorId;

    private Exception exception;

    /**
     * 关联父亲节点, 父节点可以有多个
     *
     * @param parents    parent nodes
     * @return true | false
     */
    public boolean associateParentNode(List<TaskDTO> parents)
            throws SqlParserException {
        JSONObject current = this.view().getData();
        JSONArray input = new JSONArray();
        JSONArray parentTypes = new JSONArray();
        JSONArray parentTimeStamps = new JSONArray();
        for (TaskDTO parent : parents) {
            parentTypes.add(parent.getType());
            JSONObject jsonObject = JSONObject.parseObject(parent.getDataJson());
            JSONArray parentOutput = jsonObject.getJSONArray("output");
            parentTimeStamps.add(jsonObject.getLong("lastTimeStamp"));
            if (parentOutput.size() == 0) {
                parent.setChildId("");
                throw new AutoTriggerException(
                        String.format("%s的输出表为空，自动更新失败，请配置后再执行", parent.getName()));
            }
            for (int i = 0; i < parentOutput.size(); ++i) {
                JSONObject inputJson = parentOutput.getJSONObject(i);
                inputJson.put("nodeName", parent.getName());
                input.add(inputJson);
            }
        }
        current.put("input", input);
        current.put("parentType", parentTypes);
        current.put("parentTimeStamps", parentTimeStamps);
        this.setDataJson(current.toJSONString());
        TaskVO vo = view();
        JSONObject data = vo.getData();

        if (TaskTypeEnum.TASK_TYPE_MODEL.getVal() == type) {
//TODO
            data.put("output", input);
        }
        if (TaskTypeEnum.TASK_TYPE_ALGO.getVal() == type) {
            BaseAlg alg = BaseAlg.instance(vo.getData());
            alg.defineOutput(vo);
        } else if (TaskTypeEnum.TASK_TYPE_ETL.getVal() == type) {
            BaseETL etl = BaseETL.instance(vo.getData());
            etl.defineOutput(vo);
        } else if (TaskTypeEnum.TASK_TYPE_CLEAN.getVal() == type) {
            modifyNodeName(input, StringUtils.isNotEmpty(vo.getName()) ? vo.getName() : "清洗");
            data.put("output", input);
        }
        Integer algType = data.getInteger("algType");
        if (type == TaskTypeEnum.TASK_TYPE_ETL.getVal() &&
                (ETLEnum.JOIN.getVal() == algType || ETLEnum.UNION.getVal() == algType
                        || ETLEnum.PIVOT_TABLE.getVal() == algType)) {

        } else {
            defineOutputFromInput(vo.getData(), input);
        }
        this.setDataJson(JSONObject.toJSONString(vo.getData()));

        if (vo.getException() != null) {
            this.setException(vo.getException());
        }

        return true;
    }

    public void modifyNodeName(JSONArray input, String nodeName) {
        for (int i = 0; i < input.size(); ++i) {
            JSONObject item = input.getJSONObject(i);
            item.put("nodeName", nodeName);
            input.set(i, item);
        }
    }

    /**
     * 将父节点的总行数、语义和有序类别顺序往下传
     */
    private void defineOutputFromInput(JSONObject data, JSONArray input) {
        if (CollectionUtil.isEmpty(input)) {
            return;
        }
        Long total = 0L;
        for (int i = 0; i < input.size(); i++) {
            JSONObject item = input.getJSONObject(i);
            if (item.getLong("totalRow") != null && item.getLong("totalRow") > total) {
                total = item.getLong("totalRow");
            }
        }
        JSONArray output = data.getJSONArray("output");
        if (CollectionUtil.isEmpty(output)) {
            return;
        }
        JSONObject outItem = output.getJSONObject(0);
        JSONObject inItem = input.getJSONObject(0);
        outItem.put("totalRow", total);
        outItem.put("semantic", inItem.getJSONObject("semantic"));
        if (inItem.getJSONObject("categoryOrder") != null) {
            outItem.put("categoryOrder", inItem.getJSONObject("categoryOrder"));
        }
    }

    public TaskVO view() {
        Exception tempException = null;
        if (this.getException() != null) {
            tempException = this.getException();
            this.setException(null);
        }
        TaskVO vo = DozerUtil.mapper(this, TaskVO.class);
        vo.setData(JSONObject.parseObject(this.getDataJson()));
        vo.setException(tempException);
        return vo;
    }

    /**
     * 简单的feature类型验证
     *
     * @return
     */
    public boolean validateParamTypes(List<ApiResultCode> errorCode) {
        JSONObject jsonObject = JSONObject.parseObject(this.dataJson);
        if (!jsonObject.containsKey("validate")) {
            return true;
        }
        JSONArray validate = jsonObject.getJSONArray("validate");
        if (validate.size() == 0) {
            return true;
        }
        if (StringUtils.isEmpty(parentId)) {
            return true;
        }
        JSONArray input = jsonObject.getJSONArray("input");
        if (input == null || input.size() == 0) {
            errorCode.add(ApiResultCode.TASK_VALIDATE_FAIL);
            return false;
        }
        Map<String, String> colsMap = new HashMap<>();
        for (int i = 0; i < input.size(); ++i) {
            JSONObject item = input.getJSONObject(i);
            List<String> cols = item.getJSONArray("tableCols").toJavaList(String.class)
                    .stream().map(x -> {
                        String[] tmps = x.split("\\.");
                        return tmps[tmps.length - 1];
                    }).collect(Collectors.toList());
            List<String> colsTypes = item.getJSONArray("columnTypes").toJavaList(String.class);
            for (int j = 0; j < colsTypes.size(); ++j) {
                colsMap.put(cols.get(j), colsTypes.get(j));
            }
        }

        for (int i = 0; i < validate.size(); ++i) {
            String valConf = validate.getString(i);
            String[] tmps = valConf.split(",");
            if (tmps.length != 2) {
                continue;
            }
            String key = tmps[0];
            String type = tmps[1];
            List<String> features = jsonObject.getJSONArray(key).toJavaList(String.class).stream()
                    .map(x -> {
                        String[] tmp = x.split("\\.");
                        return tmp[tmp.length - 1];
                    }).collect(Collectors.toList());
            boolean flag = ToolUtil.checkTypeConsistence(colsMap, features, type);
            if (!flag) {
                errorCode.add(ApiResultCode.TASK_VALIDATE_FAIL);
                return false;
            }
        }
        return true;
    }

    public boolean isForbidden() {
        JSONObject jsonObject = JSONObject.parseObject(this.getDataJson());
        if (jsonObject.containsKey(FORBIDDEN_FLAG)) {
            return jsonObject.getBoolean(FORBIDDEN_FLAG);
        }
        return false;
    }

    public TaskDTO cloneNew() {
        return (TaskDTO) clone();
    }

    @Override
    protected Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<WidgetDTO> toWidgets() {
        WidgetDTO dto = new WidgetDTO();
        dto.setTid(this.getId());
        String outputTable = TaskUtil.extractTableStr(this);
        List<WidgetDTO> result = Lists.newArrayList();
        JSONObject jsonObject = JSONObject.parseObject(this.getDataJson());
        if (jsonObject.containsKey("lastStatus")) {
            dto.setStatus(jsonObject.getString("lastStatus"));
        } else {
            dto.setStatus(TaskInstanceStatus.SUCCESS.toString());
        }

        JSONObject dataJson = new JSONObject();
        WidgetDTO clone = SerializationUtils.clone(dto);
        if (this.getType().equals(TASK_TYPE_DATA.getVal())) {
            //如果是数据节点
            clone.setType(WidgetTypeEnum.TASK.getDesc());
            clone.setName(this.getName() + " data table");
        } else if (this.getType().equals(TASK_TYPE_ETL.getVal()) ||
                this.getType().equals(TASK_TYPE_CLEAN.getVal()) ||
                this.getType().equals(TASK_TYPE_MODEL.getVal()) ) {
            clone.setType(WidgetTypeEnum.TASK.getDesc());
            clone.setName(this.getName() + " result table");
        } else if (this.getType().equals(TASK_TYPE_ALGO.getVal())) {
            clone.setType(WidgetTypeEnum.TASK.getDesc());
            //需要进行一次筛选，K-means、Linear-Regression、Decision Tree或PCA 四种需要添加配置参数，其他不需要
            if (isNeededAlgo(jsonObject)) {
                clone.setType(WidgetTypeEnum.CONFIG.getDesc());
                clone.setName(this.getName() + " configuration panel");
                JSONArray params = jsonObject.getJSONArray("setParams");
                dataJson.put("params", params);
                JSONObject info = new JSONObject();
                info.put("parentId", this.getParentId());
                info.put("childId", this.getChildId());
                info.put("type", this.getType());
                info.put("algType", jsonObject.getString("algType"));
                dataJson.put("info", info);
            }
        } else if (this.getType().equals(TASK_TYPE_ALGOPY.getVal())) {
            clone.setType(WidgetTypeEnum.TASK.getDesc());
            //需要进行一次筛选，K-means、Linear-Regression、Decision Tree或PCA 四种需要添加配置参数，其他不需要
            if (this.isThisTypeAlgoPy(AlgPyEnum.SIMULATE) || this.isThisTypeAlgoPy(AlgPyEnum.SIMULATE_NEW)
                || this.isThisTypeAlgoPy(AlgPyEnum.SIMULATE_INVERSE)) {
                clone.setType(WidgetTypeEnum.CONFIG.getDesc());
                clone.setName(this.getName() + " configuration panel");
                JSONArray setParams = jsonObject.getJSONArray("setParams");
                JSONArray params = new JSONArray();
                JSONObject item = setParams.getJSONObject(0);
                JSONArray formItems = item.getJSONArray("formItem");
                for (Object obj : formItems) {
                    JSONObject formItem = (JSONObject) obj;
                    JSONObject param = new JSONObject();
                    String name = formItem.getString("name");
                    param.put("default", 0);
                    param.put("name", formItem.getString("chineseName"));
                    param.put("type", formItem.getString("type"));
                    param.put("value", item.getJSONObject("formData").getFloatValue(name));
                    param.put("english_name", name);
                    param.put("tips", formItem.getString("message"));
                    params.add(param);
                }
                dataJson.put("params", params);
                JSONObject info = new JSONObject();
                info.put("parentId", this.getParentId());
                info.put("childId", this.getChildId());
                info.put("type", this.getType());
                info.put("algType", jsonObject.getString("algType"));
                dataJson.put("info", info);
            }
        } else {
            clone.setType(WidgetTypeEnum.TASK.getDesc());
            clone.setName(this.getName() + " result table");
        }

        dataJson.put("table", outputTable);
        clone.setDataJson(dataJson.toJSONString());
        result.add(clone);
        return result;
    }

    private boolean isThisTypeAlgoPy(AlgPyEnum algPyEnum) {
        if (this.getType().equals(TASK_TYPE_ALGOPY.getVal())) {
            int algType = JSONObject.parseObject(this.getDataJson()).getIntValue(DataJsonConstant.TASK_ALG_TYPE);
            return algType == algPyEnum.getVal();
        }
        return false;
    }

    private boolean isNeededAlgo(JSONObject conf) {
        int type = conf.getIntValue("algType");
        return type == AlgEnum.KMEANS.getVal() ||
                type == AlgEnum.TSNE.getVal() ||
                type == AlgEnum.PCA_DENSE.getVal() ||
                type == AlgEnum.LLE.getVal();
    }

    public boolean isJoinOrUnion() {
        if (this.getType().equals(TASK_TYPE_ETL.getVal())) {
            int algType = JSONObject.parseObject(this.getDataJson()).getIntValue(DataJsonConstant.TASK_ALG_TYPE);
            return algType == ETLEnum.JOIN.getVal() || algType == ETLEnum.UNION.getVal();
        }
        return false;
    }

    public boolean isFilter() {
        if (this.getType().equals(TASK_TYPE_ETL.getVal())) {
            int algType = JSONObject.parseObject(this.getDataJson()).getIntValue(DataJsonConstant.TASK_ALG_TYPE);
            return algType == ETLEnum.FILTER.getVal();
        }
        return false;
    }

    public boolean isThisTypeETL(ETLEnum etlEnum) {
        if (this.getType().equals(TASK_TYPE_ETL.getVal())) {
            int algType = JSONObject.parseObject(this.getDataJson()).getIntValue(DataJsonConstant.TASK_ALG_TYPE);
            return algType == etlEnum.getVal();
        }
        return false;
    }

    public boolean isThisTypeNode(TaskTypeEnum type) {
        return type.getVal().equals(this.getType());
    }

    public boolean isThisTypeAlgo(AlgEnum algEnum) {
        if (this.getType().equals(TASK_TYPE_ALGO.getVal())) {
            int algType = JSONObject.parseObject(this.getDataJson()).getIntValue(DataJsonConstant.TASK_ALG_TYPE);
            return algType == algEnum.getVal();
        }
        return false;
    }

    public List<Long> getChildIdList() {
        if (StringUtils.isNotEmpty(this.childId)) {
            String[] children = this.childId.split(",");
            return Arrays.stream(children).map(Long::parseLong).collect(Collectors.toList());
        }
        return Lists.newArrayList();
    }

    public List<Long> getParentIdList() {
        if (StringUtils.isNotEmpty(this.parentId)) {
            String[] children = this.parentId.split(",");
            return Arrays.stream(children).map(Long::parseLong).collect(Collectors.toList());
        }
        return Lists.newArrayList();
    }
}